﻿/* Original work by tombull
 * http://www.codeplex.com/uTorrentClient
 * 
 * Modified by Descention to support uTorrent Token Authentication
 * Find more about Token auth: http://forum.utorrent.com/viewtopic.php?id=58111
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using Cleverscape.UTorrentClient.WebClient.ServiceDefinition;
using System.IO;
using System.Net;
using System.Windows.Threading;
using System.ServiceModel.Description;
using System.Net.Browser;

namespace Cleverscape.UTorrentClient.WebClient
{
    /// <summary>
    /// Represents an instance of uTorrent.
    /// Uses the Web UI API to connect to uTorrent to retrieve and update information.
    /// </summary>
    public class UTorrentWebClient : IDisposable, IUTorrentData
    {
        #region Private Fields

        private bool _autoUpdate;
        //private DispatcherTimer _autoUpdateTimerSync;
        private DispatcherTimer _autoUpdateTimerAsync;
        private bool _autoUpdateAsynchronous;
        private Dispatcher _dispatcher;
        private TimeSpan _minimumTimeBetweenUpdates;

        private IUTorrentWebClient ServiceClient;
        //private WebChannelFactory<IUTorrentWebClient> ChannelFactory;

        private string _uTorrentAddress;
        private string _uTorrentUserName;
        private string _uTorrentPassword;

        private long _cacheID;
        private bool _torrentsAndLabelsListStored;
        private TorrentCollection _torrents;
        private TorrentLabelCollection _labels;
        private FeedCollection _feeds;
        private FilterCollection _filters;
        private DateTime _torrentsAndLabelsLastUpdated;

        private SettingsCollection _settings;
        private DateTime _settingsLastUpdated;

        private string _token;

        #endregion

        #region Constructors

        /// <summary>
        /// Create a new UTorrentWebClient with no auto updating
        /// </summary>
        /// <param name="ServerAddress">The address of the uTorrent Web UI. This can be expressed as a hostname (e.g. utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and a suffix '/gui/'. If these elements are not included, they will be added.</param>
        /// <param name="UserName">The username used to access the uTorrent Web UI.</param>
        /// <param name="Password">The password used to access the uTorrent Web UI.</param>
        public UTorrentWebClient(string ServerAddress, string UserName, string Password, int refreshSec)
            : this(ServerAddress, UserName, Password, false, refreshSec) { }

        /// <summary>
        /// Create a new UTorrentWebClient with optional auto updating
        /// </summary>
        /// <param name="ServerAddress">The address of the uTorrent Web UI. This can be expressed as a hostname (e.g. utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and a suffix '/gui/'. If these elements are not included, they will be added.</param>
        /// <param name="UserName">The username used to access the uTorrent Web UI.</param>
        /// <param name="Password">The password used to access the uTorrent Web UI.</param>
        /// <param name="AutoUpdate">Whether or not to auto update the UTorrentWebClient object automatically</param>
        public UTorrentWebClient(string ServerAddress, string UserName, string Password, bool AutoUpdate, int refreshSec)
            : this(ServerAddress, UserName, Password, AutoUpdate, null, refreshSec) { }

        /// <summary>
        /// Create a new UTorrentWebClient with optional auto updating and optionally passing a System.Windows.Threading.Dispatcher to handle UI events
        /// </summary>
        /// <param name="ServerAddress">The address of the uTorrent Web UI. This can be expressed as a hostname (e.g. utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and a suffix '/gui/'. If these elements are not included, they will be added.</param>
        /// <param name="UserName">The username used to access the uTorrent Web UI.</param>
        /// <param name="Password">The password used to access the uTorrent Web UI.</param>
        /// <param name="AutoUpdate">Whether or not to auto update the UTorrentWebClient object automatically</param>
        /// <param name="Dispatcher">A System.Windows.Threading.Dispatcher to handle UI events</param>
        public UTorrentWebClient(string ServerAddress, string UserName, string Password, bool AutoUpdate, Dispatcher Dispatcher, int refreshSec)
        {
            _autoUpdateAsynchronous = true;
            MinimumTimeBetweenUpdates = TimeSpan.FromSeconds(refreshSec);
            _dispatcher = Dispatcher;
            this.AutoUpdate = AutoUpdate;

            SetConnectionDetails(ServerAddress, UserName, Password);
            UpdateSettings();
            
            
        }



        public string myCookie;



        /// <summary>
        /// Change the connection details used to connect to the uTorrent Web UI
        /// </summary>
        /// <param name="ServerAddress">The address of the uTorrent Web UI. This can be expressed as a hostname (e.g. utorrent.mynetwork.com) or an IP address (e.g. 10.11.12.13). This can optionally include a preceeding 'http://' and a suffix '/gui/'. If these elements are not included, they will be added.</param>
        /// <param name="UserName">The username used to access the uTorrent Web UI.</param>
        /// <param name="Password">The password used to access the uTorrent Web UI.</param>
        public void SetConnectionDetails(string ServerAddress, string UserName, string Password)
        {
            _torrentsAndLabelsListStored = false;

            _uTorrentAddress = ProcessServerAddress(ServerAddress);
            _uTorrentUserName = UserName;
            _uTorrentPassword = Password;

            WebRequest.RegisterPrefix("http://",
                WebRequestCreator.ClientHttp );
       
            //CustomBinding uTorrentCustomBinding = new CustomBinding(
            //    //new WebMessageEncodingBindingElement() { ContentTypeMapper = new JsonContentTypeMapper() },
            //    new HttpTransportBindingElement() { ManualAddressing = true, AuthenticationScheme = System.Net.AuthenticationSchemes.Basic, Realm = "uTorrent", AllowCookies = true }
            //    );
            //EndpointAddress uTorrentEndpointAddress = new EndpointAddress(_uTorrentAddress);

            //ChannelFactory = new WebChannelFactory<IUTorrentWebClient>(uTorrentCustomBinding);
            //ChannelFactory.Endpoint.Address = uTorrentEndpointAddress;
            //ChannelFactory.Credentials.UserName.UserName = _uTorrentUserName;
            //ChannelFactory.Credentials.UserName.Password = _uTorrentPassword;

            HttpCookieContainerBindingElement ccbe = new HttpCookieContainerBindingElement();
            CustomBinding binding = new CustomBinding(
                ccbe,
                new WebMessageEncodingBindingElement(),
                new HttpTransportBindingElement { ManualAddressing = true, MaxReceivedMessageSize = 2147483647 });


            
            //string address = GetServiceAddress();
            ChannelFactory<IUTorrentWebClient> factory = new ChannelFactory<IUTorrentWebClient>(binding, new EndpointAddress(_uTorrentAddress));
            



            ClientCredentials loginCredentials = new ClientCredentials();
            loginCredentials.UserName.UserName = _uTorrentUserName;
            loginCredentials.UserName.Password = _uTorrentPassword;


            
            var defaultCreds = factory.Endpoint.Behaviors.Find<ClientCredentials>();
            factory.Endpoint.Behaviors.Remove(defaultCreds);
            factory.Endpoint.Behaviors.Add(loginCredentials);

            factory.Endpoint.Behaviors.Add(new WebHttpBehaviorWithJson());
            factory.Endpoint.Behaviors.Add(new MyInspector());
            
            IUTorrentWebClient proxy = factory.CreateChannel();
            
            //using (OperationContextScope contextScope = new OperationContextScope((IContextChannel)proxy))
            //{

            //    var bc = Encoding.UTF8.GetBytes(@"admin" + ":" + "youwho11");

            //    HttpRequestMessageProperty httpProps = new HttpRequestMessageProperty();



            //    httpProps.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(bc);




            //    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpProps;
            //}


            ((IClientChannel)proxy).Closed += delegate { factory.Close(); };


            ServiceClient = proxy;

            this.getToken();
        }

        public void getToken()
        {
            if (Busy)
                return;
            SetAuthentication();
            Busy = true;

            using (OperationContextScope contextScope = new OperationContextScope((IContextChannel)ServiceClient))
            {

                var bc = Encoding.UTF8.GetBytes(@"admin" + ":" + "youwho11");

                HttpRequestMessageProperty httpProps = new HttpRequestMessageProperty();



                httpProps.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(bc);




                OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpProps;
                ServiceClient.BegingetToken(new AsyncCallback(ar =>
                {

                    IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;

                    string result = null;
                    result = p.EndgetToken(ar);
                    Regex r = new Regex(".*<div[^>]*id=[\"\']token[\"\'][^>]*>([^<]*)</div>.*");
                    Match m = r.Match(result);
                    _token = m.Result("$1");
                    Busy = false;

                }), ServiceClient);
            }
            
            
        }

        private void SetAuthentication()
        {
            OperationContextScope contextScope = new OperationContextScope((IContextChannel)ServiceClient);


            var bc = Encoding.UTF8.GetBytes(@"admin" + ":" + "youwho11");

            HttpRequestMessageProperty httpProps = new HttpRequestMessageProperty();



            httpProps.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(bc);




            OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpProps;
        }

        private void SetHeaders(System.Net.WebClient wc, string boundry)
        {
            var bc = Encoding.UTF8.GetBytes(@"admin" + ":" + "youwho11");
            wc.Headers[HttpRequestHeader.Authorization] = "Basic " + Convert.ToBase64String(bc);
            wc.Headers[HttpRequestHeader.ContentType] = "multipart/form-data; boundary="+boundry;
            wc.Headers[HttpRequestHeader.Accept] = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*";
        }

        #endregion

        #region Internal Properties

        internal Dispatcher Dispatcher
        {
            get
            {
                return _dispatcher;
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Whether or not to automatically update the list of torrents and labels.
        /// If true, the collections of torrents and labels will be updated at intervals of MinimumTimeBetweenUpdates
        /// </summary>
        public bool AutoUpdate
        {
            get
            {
                return _autoUpdate;
            }
            set
            {
                _autoUpdate = value;
                if (_autoUpdate)
                {
                    if (_autoUpdateAsynchronous)
                    {
                        if (_autoUpdateTimerAsync == null)
                        {
                            _autoUpdateTimerAsync = new DispatcherTimer();
                            _autoUpdateTimerAsync.Interval = new TimeSpan(0,0,0,0, (int)MinimumTimeBetweenUpdates.TotalMilliseconds);//System.Timers.Timer(MinimumTimeBetweenUpdates.TotalMilliseconds);
                            _autoUpdateTimerAsync.Tick += new EventHandler(_autoUpdateTimerAsync_Tick); //.Elapsed += new ElapsedEventHandler(AutoUpdateTimer_Elapsed);

                        }
                        else
                        {
                            _autoUpdateTimerAsync.Interval = new TimeSpan(0, 0, 0, 0, (int)MinimumTimeBetweenUpdates.TotalMilliseconds);//System.Timers.Timer(MinimumTimeBetweenUpdates.TotalMilliseconds);
                        }
                        _autoUpdateTimerAsync.Start();
                    }
                    else
                    {
                        //if (_autoUpdateTimerSync == null)
                        //{
                        //    _autoUpdateTimerSync = new System.Windows.Forms.Timer();
                        //    _autoUpdateTimerSync.Interval = Convert.ToInt32(MinimumTimeBetweenUpdates.TotalMilliseconds);
                        //    _autoUpdateTimerSync.Tick += new EventHandler(AutoUpdateTimer_Tick);
                        //}
                        //else
                        //{
                        //    _autoUpdateTimerSync.Interval = Convert.ToInt32(MinimumTimeBetweenUpdates.TotalMilliseconds);
                        //}
                        //_autoUpdateTimerSync.Start();
                    }
                }
                else
                {
                    //if (_autoUpdateTimerSync != null)
                    //{
                    //    _autoUpdateTimerSync.Stop();
                    //}
                    if (_autoUpdateTimerAsync != null)
                    {
                        _autoUpdateTimerAsync.Stop();
                    }
                }
            }
        }

        void _autoUpdateTimerAsync_Tick(object sender, EventArgs e)
        {
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        private void AutoUpdateTimer_Tick(object sender, EventArgs e)
        {
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        //private void AutoUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        //{
        //    GetTorrentsAndLabelsAndRSSAndFilters(true);
        //}

        /// <summary>
        /// The minimum time between updates or the time between automatic updates.
        /// If information is requested from an object that gets its information from uTorrent, and the information is less than MinimumTimeBetweenUpdates old, then the cached information will be returned.
        /// </summary>
        public TimeSpan MinimumTimeBetweenUpdates
        {
            get
            {
                return _minimumTimeBetweenUpdates;
            }
            set
            {
                _minimumTimeBetweenUpdates = value;
                if (AutoUpdate)
                {
                    //if (_autoUpdateTimerSync != null)
                    //{
                    //    _autoUpdateTimerSync.Interval = Convert.ToInt32(_minimumTimeBetweenUpdates.TotalMilliseconds);
                    //}
                    if (_autoUpdateTimerAsync != null)
                    {
                        _autoUpdateTimerAsync.Interval = new TimeSpan(0, 0, 0, 0, (int)_minimumTimeBetweenUpdates.TotalMilliseconds);
                    }
                }
            }
        }

        /// <summary>
        /// The collection of torrents currently managed by uTorrent
        /// </summary>
        public TorrentCollection Torrents
        {
            get
            {
                GetTorrentsAndLabelsAndRSSAndFilters();
                return _torrents;
            }
        }

        /// <summary>
        /// The collection of torrent labels currently managed by uTorrent
        /// </summary>
        public TorrentLabelCollection Labels
        {
            get
            {
                GetTorrentsAndLabelsAndRSSAndFilters();
                return _labels;
            }
        }

        /// <summary>
        /// The collection of feeds currently managed by uTorrent
        /// </summary>
        public FeedCollection Feeds
        {
            get
            {
                GetTorrentsAndLabelsAndRSSAndFilters();
                return _feeds;
            }
        }

        /// <summary>
        /// The collection of feeds currently managed by uTorrent
        /// </summary>
        public FilterCollection Filters
        {
            get
            {
                GetTorrentsAndLabelsAndRSSAndFilters();
                return _filters;
            }
        }

        /// <summary>
        /// The collection of uTorrent settings
        /// </summary>
        public SettingsCollection Settings
        {
            get
            {
                UpdateSettings();
                return _settings;
            }
        }


        #endregion

        #region Public Methods

        /// <summary>
        /// Starts a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to start</param>
        public void TorrentStart(Torrent Torrent)
        {
            ServiceClient.BeginStartTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Stops a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to stop</param>
        public void TorrentStop(Torrent Torrent)
        {
            ServiceClient.BeginStopTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Pauses a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to pause</param>
        public void TorrentPause(Torrent Torrent)
        {
            ServiceClient.BeginPauseTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Un-pauses a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to un-pause</param>
        public void TorrentUnPause(Torrent Torrent)
        {
            ServiceClient.BeginUnPauseTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Force starts a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to force start</param>
        public void TorrentForceStart(Torrent Torrent)
        {
            ServiceClient.BeginForceStartTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Re-checks a torrent
        /// </summary>
        /// <param name="Torrent">The torrent to re-check</param>
        public void TorrentReCheck(Torrent Torrent)
        {
            ServiceClient.BeginRecheckTorrent(Torrent.Hash, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Removes a torrent from uTorrent
        /// </summary>
        /// <param name="Torrent">The torrent to remove</param>
        /// <param name="RemoveData">Whether or not the data is removed</param>
        public void TorrentRemove(Torrent Torrent, bool RemoveData)
        {
            if (RemoveData)
            {
                ServiceClient.BeginRemoveTorrentAndData(Torrent.Hash, _token, null, ServiceClient);
            }
            else
            {
                ServiceClient.BeginRemoveTorrent(Torrent.Hash, _token, null, ServiceClient);
            }
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Removes a torrent from uTorrent and keeps the data
        /// </summary>
        /// <param name="Torrent">The torrent to remove</param>
        public void TorrentRemove(Torrent Torrent)
        {
            TorrentRemove(Torrent, false);
        }

        /// <summary>
        /// Removes a feed from uTorrent
        /// </summary>
        /// <param name="Feed">The feed to remove</param>
        public void FeedRemove(Feed Feed)
        {
            ServiceClient.BeginRemoveRss(Feed.Ident, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Update or Add a feed on uTorrent
        /// </summary>
        /// <param name="Feed">The feed to update or add</param>
        public void FeedUpdateAdd(Feed Feed)
        {
            ServiceClient.BeginUpdateAddRss(
                Feed.Ident,
                Feed.Url,
                Feed.Alias,
                Feed.Subscribed,
                Feed.SmartEpFilter,
                Feed.Enabled,
                1,
                _token, null, ServiceClient);
                
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Removes a filter from uTorrent
        /// </summary>
        /// <param name="Filter">The filter to remove</param>
        public void FilterRemove(Filter Filter)
        {
            ServiceClient.BeginRemoveFilter(Filter.Ident, _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Update or Add a filter on uTorrent
        /// </summary>
        /// <param name="Filter">The filter to update or add</param>
        public void FilterUpdateAdd(Filter Filter)
        {
            int filterIdent = (Filter.Ident <= 0) ? -1 : Filter.Ident;
            ServiceClient.BeginUpdateAddFilter(filterIdent,
                Filter.Name,
                Filter.Directory,
                Filter.EpisodeFilterStr,
                Filter.FilterText,
                Filter.NotFilter,
                Filter.Label,
                Filter.Quality.QInt,
                Filter.EpisodeFilter,
                Filter.FilterMatchesOriginalName,
                Filter.GiveHighestPrio,
                Filter.SmartEpFilter,
                Filter.DontStartAuto,
                (int)Filter.PostponeMode,
                Filter.Feed,
                _token, null, ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Adds a torrent to uTorrent from a url
        /// </summary>
        /// <param name="Url">The url to download the .torrent file</param>
        public void AddTorrentFromUrl(string Url)
        {
            ServiceClient.BeginAddTorrentFromUrl(Url, _token, new AsyncCallback(ar => { }), ServiceClient);
            GetTorrentsAndLabelsAndRSSAndFilters(true);
        }

        /// <summary>
        /// Adds a torrent to uTorrent from a file
        /// </summary>
        /// <param name="FileName">The name of the file to add</param>
        public void AddTorrent(string FileName)
        {
            AddTorrent(File.OpenRead(FileName), FileName);
        }

        /// <summary>
        /// Adds a torrent to uTorrent from a stream
        /// </summary>
        /// <param name="TorrentStream">The stream containing the torrent to add</param>
        public void AddTorrent(Stream TorrentStream)
        {
            AddTorrent(TorrentStream, "C:\\Torrent.torrent");
        }

        /// <summary>
        /// Adds a torrent to uTorrent from a stream, uploading it with a specified file name
        /// </summary>
        /// <param name="TorrentStream">The stream containing the torrent to add</param>
        /// <param name="FileName">The filename to upload the torrent using</param>
        public void AddTorrent(Stream TorrentStream, string FileName)
        {
            UploadFile(FileName, TorrentStream);
        }

        private void UploadFile(string fileName, Stream data)
        {
            UriBuilder ub = new UriBuilder(_uTorrentAddress + "?token=" + _token + "&action=add-file&download_dir=0&path=");
            //ub.Query = string.Format("filename={0}", fileName);
            System.Net.WebClient c = new System.Net.WebClient();
            string BoundaryString = "---------------------------7db27c1c2e1ebc";//Guid.NewGuid().ToString("N");
            SetHeaders(c, BoundaryString);
            c.OpenWriteCompleted += (sender, e) =>
            {

                PushData(ReadFully(data), fileName, e.Result, BoundaryString);
                e.Result.Close();
                data.Close();
            };
            c.OpenWriteAsync(ub.Uri, "POST");
        }

        private void PushData(byte[] FileBytes, string FileName, Stream output, string BoundaryString)
        {
            byte[] preBytes = Encoding.UTF8.GetBytes(String.Format("--{0}\r\nContent-Disposition: form-data; name=\"torrent_file\"; filename=\"{1}\"\r\nContent-Type: application/x-bittorrent\r\n\r\n", BoundaryString,  FileName));
            output.Write(preBytes, 0, preBytes.Length);
            output.Write(FileBytes, 0, FileBytes.Length);
            byte[] endBytes = Encoding.UTF8.GetBytes(String.Format("\r\n--{0}--\r\n", BoundaryString));
            output.Write(endBytes, 0, endBytes.Length);
        }

        public static byte[] StrToByteArray(string str)
        {
            System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
            return encoding.GetBytes(str);
        }

        public static byte[] ReadFully(Stream input) 
        { 
            byte[] buffer = new byte[16 * 1024]; 
            using (MemoryStream ms = new MemoryStream()) 
            { 
                int read; 
                while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 
                { 
                    ms.Write(buffer, 0, read); 
                } 
                return ms.ToArray(); 
            } 
        
        }




        #region IDisposable implementation

        public void Dispose()
        {
            ServiceClient = null;
            //ChannelFactory.Close();
            //ChannelFactory = null;
        }

        #endregion

        #endregion

        #region Helper Methods

        private string ProcessServerAddress(string ServerAddress)
        {
            string ServerAddressProcessed = ServerAddress.ToLowerInvariant();
            if (!ServerAddressProcessed.StartsWith("http://"))
            {
                ServerAddressProcessed = String.Format("http://{0}", ServerAddressProcessed);
            }
            if (!ServerAddressProcessed.EndsWith("/"))
            {
                ServerAddressProcessed = String.Format("{0}/", ServerAddressProcessed);
            }
            if (!ServerAddressProcessed.EndsWith("/gui/"))
            {
                ServerAddressProcessed = String.Format("{0}gui/", ServerAddressProcessed);
            }
            return ServerAddressProcessed;
        }

        #endregion

        #region Public Events

        /// <summary>
        /// Called when a torrent enters the started state
        /// </summary>
        public event TorrentEventHandler TorrentStarted;

        /// <summary>
        /// Called when a torrent enters the stopped state
        /// </summary>
        public event TorrentEventHandler TorrentStopped;

        /// <summary>
        /// Called when a torrent is paused
        /// </summary>
        public event TorrentEventHandler TorrentPaused;

        /// <summary>
        /// Called when a torrent is unpaused
        /// </summary>
        public event TorrentEventHandler TorrentUnPaused;

        /// <summary>
        /// Called when a torrent has finished downloading
        /// </summary>
        public event TorrentEventHandler TorrentFinishedDownloading;

        /// <summary>
        /// Called when a new torrent is added
        /// </summary>
        public event TorrentEventHandler TorrentAdded;

        /// <summary>
        /// Called when the list of torrents is updated
        /// </summary>
        public event EventHandler TorrentsUpdated;

        public event EventHandler TickerException;

        internal enum EventType
        {
            Started,
            Stopped,
            Paused,
            UnPaused,
            FinishedDownloading,
            Added,
            Updated
        }

        internal void CallEvent(EventType Type, Torrent Torrent)
        {
            CallEvent(Type, Torrent, false);
        }

        internal void CallEvent(EventType Type, Torrent Torrent, bool FromDispatcher)
        {
            if (!FromDispatcher && _dispatcher != null)
            {
                _dispatcher.BeginInvoke(new CallEventDelegate(CallEvent),  new object[] { Type, Torrent, true });
            }
            else
            {
                try
                {
                    switch (Type)
                    {
                        case EventType.Started:
                            if (TorrentStarted != null)
                            {
                                TorrentStarted(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.Stopped:
                            if (TorrentStopped != null)
                            {
                                TorrentStopped(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.Paused:
                            if (TorrentPaused != null)
                            {
                                TorrentPaused(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.UnPaused:
                            if (TorrentUnPaused != null)
                            {
                                TorrentUnPaused(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.FinishedDownloading:
                            if (TorrentFinishedDownloading != null)
                            {
                                TorrentFinishedDownloading(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.Added:
                            if (TorrentAdded != null)
                            {
                                TorrentAdded(this, new TorrentEventArgs(Torrent));
                            }
                            break;
                        case EventType.Updated:
                            if (TorrentsUpdated != null)
                            {
                                TorrentsUpdated(this, new EventArgs());
                            }
                            break;
                        default:
                            break;
                    }
                }
                catch (Exception)
                {
                }
            }
        }

        private delegate void CallEventDelegate(EventType Type, Torrent Torrent, bool FromDispatcher);

        #endregion

        #region Getting Torrents And Labels

        private void GetTorrentsAndLabelsAndRSSAndFilters()
        {
            GetTorrentsAndLabelsAndRSSAndFilters(false);
        }

        private void GetTorrentsAndLabelsAndRSSAndFilters(bool ForceUpdate)
        {
            if (_torrentsAndLabelsLastUpdated == null || _torrentsAndLabelsLastUpdated.Add(MinimumTimeBetweenUpdates) <= DateTime.Now || ForceUpdate)
            {
                try
                {
                    if (_torrentsAndLabelsListStored && _cacheID > 0)
                    {
                        GetTorrentsAndLabelsAndRSSAndFiltersCached();
                    }
                    else
                    {
                        if (_torrentsAndLabelsListStored)
                        {
                            GetTorrentsAndLabelsAndRSSAndFiltersUpdate();
                        }
                        else
                        {
                            GetTorrentsAndLabelsAndRSSAndFiltersFresh();
                        }
                    }
                }
                catch (Exception ex)
                {
                    //AutoUpdate = false;
                    if (this.TickerException != null)
                        this.TickerException(ex, null);
                }
                CallEvent(EventType.Updated, null);
            }
        }

        private bool Busy = false;

        private void GetTorrentsAndLabelsAndRSSAndFiltersCached()
        {
            if (Busy)
                return;
            SetAuthentication();
            Busy = true;
            ServiceClient.BeginGetUpdatedTorrentsAndLabelsAndRSSAndFilters(_cacheID.ToString(), _token, new AsyncCallback(ar =>
            {
                IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                UpdatedTorrentsAndLabels UpdatedTorrents = p.EndGetUpdatedTorrentsAndLabelsAndRSSAndFilters(ar);

                _torrents.Parse(UpdatedTorrents.RemovedTorrents, UpdatedTorrents.ChangedTorrents);
                _labels.Parse(UpdatedTorrents.Labels);
                _feeds.Parse(UpdatedTorrents.RemovedFeeds, UpdatedTorrents.ChangedFeeds);
                _filters.Parse(UpdatedTorrents.RemovedFilters, UpdatedTorrents.ChangedFilters);
                SetCache(UpdatedTorrents.CacheID);
                Busy = false;
            }), ServiceClient);
        }

        private void GetTorrentsAndLabelsAndRSSAndFiltersUpdate()
        {
            GetTorrentsAndLabelsAndRSSAndFiltersUpdate(null);
        }

        private void GetTorrentsAndLabelsAndRSSAndFiltersUpdate(TorrentsList TorrentsToProcess)
        {
            try
            {
                if (Busy)
                    return;
                SetAuthentication();
                Busy = true;
                ServiceClient.BeginGetAllTorrentsAndLabelsAndRSSAndFilters(_token, new AsyncCallback((ar) =>
                    {
                        IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                        TorrentsAndLabels CurrentTorrents = p.EndGetAllTorrentsAndLabelsAndRSSAndFilters(ar);

                        _torrents.Parse(CurrentTorrents.Torrents, false);
                        _labels.Parse(CurrentTorrents.Labels);
                        _feeds.Parse(CurrentTorrents.Feeds, false);
                        _filters.Parse(CurrentTorrents.Filters, false);
                        SetCache(CurrentTorrents.CacheID);
                        Busy = false;
                    }), ServiceClient);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        protected bool initialLoadMarker = false;
        public event EventHandler InitialLoadComplete;
        

        private void GetTorrentsAndLabelsAndRSSAndFiltersFresh()
        {
            try
            {
                //ServiceClient.getToken();
                if (_token != null)
                {
                    if (Busy)
                        return;
                    SetAuthentication();
                    Busy = true;
                    ServiceClient.BeginGetAllTorrentsAndLabelsAndRSSAndFilters(_token, new AsyncCallback(ar =>
                    {

                        IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                        TorrentsAndLabels CurrentTorrents = p.EndGetAllTorrentsAndLabelsAndRSSAndFilters(ar);

                        _torrents = new TorrentCollection(this);
                        _torrents.TorrentChanged += new TorrentCollection.TorrentChangedEvent(_torrents_TorrentChanged);
                        _torrents.Parse(CurrentTorrents.Torrents, true);

                        _labels = new TorrentLabelCollection();
                        _labels.Parse(CurrentTorrents.Labels);

                        _feeds = new FeedCollection(this);
                        _feeds.Parse(CurrentTorrents.Feeds, true);

                        _filters = new FilterCollection(this);
                        _filters.Parse(CurrentTorrents.Filters, true);

                        SetCache(CurrentTorrents.CacheID);
                        if (!initialLoadMarker)
                        {
                            initialLoadMarker = true;
                            if (this.InitialLoadComplete != null)
                                this.InitialLoadComplete(this, null);
                        }
                        Busy = false;

                    }), ServiceClient);
                }

            }
            catch //(Exception ex)
            {
                return;
            }
        }

        

        public event TorrentCollection.TorrentChangedEvent TorrentChanged;

        void _torrents_TorrentChanged(Torrent t, string PropertyName)
        {
            if (TorrentChanged != null)
                TorrentChanged(t, PropertyName);
        }

        private void SetCache(string CacheIDString)
        {
            long ParsedCacheID = 0;
            if (long.TryParse(CacheIDString, out ParsedCacheID) && ParsedCacheID > 0)
            {
                _cacheID = ParsedCacheID;
            }
            else
            {
                _cacheID = 0;
            }
            _torrentsAndLabelsListStored = true;
            _torrentsAndLabelsLastUpdated = DateTime.Now;
        }

        #endregion

        #region Getting Settings

        private void UpdateSettings()
        {
            UpdateSettings(false);
        }

        private void UpdateSettings(bool ForceUpdate)
        {
            if (_settingsLastUpdated == null || _settingsLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now || ForceUpdate)
            {
                ServiceClient.BeginGetSettings(_token, new AsyncCallback(ar =>
                {
                    IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                    UTorrentSettings CurrentSettings = p.EndGetSettings(ar);//ServiceClient.GetSettings(_token);
                    if (_settings == null)
                    {
                        _settings = new SettingsCollection();
                    }
                    _settings.ParseSettings(CurrentSettings.Settings, this);
                    _settingsLastUpdated = DateTime.Now;
                }), ServiceClient);
            }
        }

        internal void SetSetting(SettingBase Setting)
        {
            object value = null;
            switch (Setting.Type)
            {
                case SettingType.Integer:
                case SettingType.String:
                    value = ((SettingString)Setting).Value;
                    break;
                case SettingType.Boolean:
                    value = ((SettingBoolean)Setting).Value ? "true" : "false";
                    break;
                default:
                    break;
            }
            ServiceClient.BeginSetStringSetting(Setting.Name, value.ToString(), _token, new AsyncCallback(ar =>
            {
                //IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                //p.EndSetStringSetting(ar);
                
            }), ServiceClient);

            UpdateSettings(true);
        }

        #endregion

        #region Getting And Setting Torrent Files

        internal void UpdateTorrentFiles(Torrent TorrentToUpdate, bool ForceUpdate)
        {
            if (TorrentToUpdate.FilesLastUpdated == null || TorrentToUpdate.FilesLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now || ForceUpdate)
            {
                if (TorrentToUpdate.FileList == null)
                {
                    TorrentToUpdate.FileList = new TorrentFileCollection(TorrentToUpdate);
                }
                ServiceClient.BeginGetFiles(TorrentToUpdate.Hash, _token, new AsyncCallback(ar => {

                    IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                    TorrentFiles tFiles = p.EndGetFiles(ar);

                    TorrentToUpdate.FileList.ParseFiles(tFiles.Files, TorrentToUpdate);
                    TorrentToUpdate.FilesLastUpdated = DateTime.Now;
                }), ServiceClient);
            }
        }

        internal void UpdateTorrentFiles(Torrent TorrentToUpdate)
        {
            UpdateTorrentFiles(TorrentToUpdate, false);
        }

        internal void SetTorrentFilePriority(TorrentFile FileToUpdate, int Priority)
        {
            ServiceClient.BeginSetFilePriority(
                FileToUpdate.ParentCollection.ParentTorrent.Hash,
                FileToUpdate.Index,
                Priority, _token, null, ServiceClient);
            UpdateTorrentFiles(FileToUpdate.ParentCollection.ParentTorrent, true);
        }

        #endregion

        #region Getting And Setting Torrent Properties

        internal void UpdateTorrentProperties(Torrent TorrentToUpdate, bool ForceUpdate)
        {
            if (TorrentToUpdate.PropertiesLastUpdated == null || TorrentToUpdate.PropertiesLastUpdated.Add(MinimumTimeBetweenUpdates) < DateTime.Now || ForceUpdate)
            {
                ServiceClient.BeginGetProperties(TorrentToUpdate.Hash, _token, new AsyncCallback(ar =>
                {
                    IUTorrentWebClient p = (IUTorrentWebClient)ar.AsyncState;
                    TorrentProperties tProperties = p.EndGetProperties(ar);

                    TorrentPropertiesList[] CurrentProperties = tProperties.Properties;
                    if (CurrentProperties.Length != 1)
                    {
                        throw new FormatException("The array of torrent properties was not in the expected format.");
                    }
                    TorrentToUpdate.ParseProperties(CurrentProperties[0]);
                    TorrentToUpdate.PropertiesLastUpdated = DateTime.Now;
                }), ServiceClient);
            }
        }

        internal void UpdateTorrentProperties(Torrent TorrentToUpdate)
        {
            UpdateTorrentProperties(TorrentToUpdate, false);
        }

        internal void SetTorrentProperty(Torrent TorrentToUpdate, string PropertyName, int PropertyValue)
        {
            ServiceClient.BeginSetIntegerProperty(TorrentToUpdate.Hash, PropertyName, PropertyValue, _token, null, ServiceClient);
        }

        internal void SetTorrentProperty(Torrent TorrentToUpdate, string PropertyName, long PropertyValue)
        {
            ServiceClient.BeginSetLongProperty(TorrentToUpdate.Hash, PropertyName, PropertyValue, _token, null, ServiceClient);
        }

        internal void SetTorrentProperty(Torrent TorrentToUpdate, string PropertyName, string PropertyValue)
        {
            ServiceClient.BeginSetStringProperty(TorrentToUpdate.Hash, PropertyName, PropertyValue, _token, null, ServiceClient);
        }

        #endregion
    }

    #region Torrent Events

    /// <summary>
    /// Event arguments for a torrent event
    /// </summary>
    public class TorrentEventArgs : EventArgs
    {
        private Torrent _torrent;

        /// <summary>
        /// The torrent to which the event is referring
        /// </summary>
        public Torrent Torrent
        {
            get
            {
                return _torrent;
            }
        }

        internal TorrentEventArgs(Torrent Torrent)
            : base()
        {
            _torrent = Torrent;
        }
    }

    public delegate void TorrentEventHandler(object sender, TorrentEventArgs e);

    #endregion


    public interface IUTorrentData
    {
        bool AutoUpdate { get; set; }
        FeedCollection Feeds { get; }
        FilterCollection Filters { get; }
        TorrentLabelCollection Labels { get; }
        TimeSpan MinimumTimeBetweenUpdates { get; set; }
        SettingsCollection Settings { get; }
        TorrentCollection Torrents { get; }

        event EventHandler TickerException;
        event TorrentEventHandler TorrentAdded;
        event TorrentCollection.TorrentChangedEvent TorrentChanged;
        event TorrentEventHandler TorrentFinishedDownloading;
        event TorrentEventHandler TorrentPaused;
        event TorrentEventHandler TorrentStarted;
        event TorrentEventHandler TorrentStopped;
        event EventHandler TorrentsUpdated;
        event TorrentEventHandler TorrentUnPaused;
        event EventHandler InitialLoadComplete;

        void AddTorrent(Stream TorrentStream);
        void AddTorrent(string FileName);
        void AddTorrent(Stream TorrentStream, string FileName);
        void AddTorrentFromUrl(string Url);
        void Dispose();
        void FeedRemove(Feed Feed);
        void FeedUpdateAdd(Feed Feed);
        void FilterRemove(Filter Filter);
        void FilterUpdateAdd(Filter Filter);
        void getToken();
        void SetConnectionDetails(string ServerAddress, string UserName, string Password);
        void TorrentForceStart(Torrent Torrent);
        void TorrentPause(Torrent Torrent);
        void TorrentReCheck(Torrent Torrent);
        void TorrentRemove(Torrent Torrent);
        void TorrentRemove(Torrent Torrent, bool RemoveData);
        void TorrentStart(Torrent Torrent);
        void TorrentStop(Torrent Torrent);
        void TorrentUnPause(Torrent Torrent);
    }
}
